package spass.scene.forms;

import spass.geometry.Point;
import spass.geometry.Ray;
import spass.geometry.Vector;

public class Cylinder implements Form {

    private final Point center;
    private final Vector axis;
    private final double radius;

    public Cylinder(Point center, Vector axis, double radius) {
        if (axis.getX() != 0.0 || axis.getZ() != 0.0)
            throw new UnsupportedOperationException("only vertical axis supported");

        this.center = center;
        this.axis = axis;
        this.radius = radius;
    }

    @Override
    public Point firstIntersectionPoint(Ray ray) {
        Point p = ray.getOrigin().substract(center);
        Vector v = ray.getDirection();

        double vx = v.getX();
        double vz = v.getZ();
        double px = p.getX();
        double pz = p.getZ();

        double a = vx * vx + vz * vz;
        double b = 2 * px * vx + 2 * pz * vz;
        double c = px * px + pz * pz - radius * radius;

        double discriminant = b * b - 4 * a * c;
        if (discriminant >= 0.0) {
            double distance = (-b - Math.sqrt(discriminant)) / (2 * a);
            if (distance > 0.0) {
                Vector scaledDirection = v.scalarMultiply(distance);
                Point endPoint = new Point(scaledDirection.getX(), scaledDirection.getY(), scaledDirection.getZ());
                Point intersectionPoint = endPoint.add(ray.getOrigin());

                double axisStart = center.getY();
                double axisEnd = axisStart + axis.getY();

                if (numberBetween(intersectionPoint.getY(), axisStart, axisEnd)) {
                    return intersectionPoint;
                }
            }
        }

        return null;
    }

    private boolean numberBetween(double number, double a, double b) {
        double min = Math.min(a, b);
        double max = Math.max(a, b);

        return min <= number && number <= max;
    }

    @Override
    public Vector surfaceNormalAt(Point point) {
        Point pointOnCenterHeight = new Point(point.getX(), center.getY(), point.getZ());
        Point difference = pointOnCenterHeight.substract(center);

        return new Vector(difference.getX(), difference.getY(), difference.getZ());
    }

}
